using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using Verse;
using Verse.AI;

namespace RimWorld;

public abstract class WorkGiver_ConstructDeliverResources : WorkGiver_Scanner
{
	private static List<Thing> resourcesAvailable = new List<Thing>();

	private static List<ThingDefCountClass> missingResources = new List<ThingDefCountClass>();

	private const float MultiPickupRadius = 5f;

	private const float NearbyConstructScanRadius = 8f;

	protected static string ForbiddenLowerTranslated;

	protected static string NoPathTranslated;

	public override Danger MaxPathDanger(Pawn pawn)
	{
		return Danger.Deadly;
	}

	public static void ResetStaticData()
	{
		ForbiddenLowerTranslated = "ForbiddenLower".Translate();
		NoPathTranslated = "NoPath".Translate();
	}

	private static bool ResourceValidator(Pawn pawn, ThingDefCountClass need, Thing th)
	{
		if (th.def != need.thingDef)
		{
			return false;
		}
		if (th.IsForbidden(pawn))
		{
			return false;
		}
		if (!pawn.CanReserve(th))
		{
			return false;
		}
		return true;
	}

	protected Job ResourceDeliverJobFor(Pawn pawn, IConstructible c, bool canRemoveExistingFloorUnderNearbyNeeders = true)
	{
		if (c is Blueprint_Install install)
		{
			return InstallJob(pawn, install);
		}
		missingResources.Clear();
		foreach (ThingDefCountClass need2 in c.MaterialsNeeded())
		{
			if (!pawn.Map.itemAvailability.ThingsAvailableAnywhere(need2, pawn))
			{
				missingResources.Add(need2);
				if (FloatMenuMakerMap.makingFor != pawn)
				{
					break;
				}
				continue;
			}
			Thing foundRes = GenClosest.ClosestThingReachable(pawn.Position, pawn.Map, ThingRequest.ForDef(need2.thingDef), PathEndMode.ClosestTouch, TraverseParms.For(pawn), 9999f, (Thing r) => ResourceValidator(pawn, need2, r));
			if (foundRes != null)
			{
				FindAvailableNearbyResources(foundRes, pawn, out var resTotalAvailable);
				int neededTotal;
				Job jobToMakeNeederAvailable;
				HashSet<Thing> hashSet = FindNearbyNeeders(pawn, need2, c, resTotalAvailable, canRemoveExistingFloorUnderNearbyNeeders, out neededTotal, out jobToMakeNeederAvailable);
				if (jobToMakeNeederAvailable != null)
				{
					return jobToMakeNeederAvailable;
				}
				hashSet.Add((Thing)c);
				Thing thing = hashSet.MinBy((Thing nee) => IntVec3Utility.ManhattanDistanceFlat(foundRes.Position, nee.Position));
				hashSet.Remove(thing);
				int num = 0;
				int num2 = 0;
				do
				{
					num += resourcesAvailable[num2].stackCount;
					num2++;
				}
				while (num < neededTotal && num2 < resourcesAvailable.Count);
				resourcesAvailable.RemoveRange(num2, resourcesAvailable.Count - num2);
				resourcesAvailable.Remove(foundRes);
				Job job = JobMaker.MakeJob(JobDefOf.HaulToContainer);
				job.targetA = foundRes;
				job.targetQueueA = new List<LocalTargetInfo>();
				for (num2 = 0; num2 < resourcesAvailable.Count; num2++)
				{
					job.targetQueueA.Add(resourcesAvailable[num2]);
				}
				job.targetB = thing;
				if (hashSet.Count > 0)
				{
					job.targetQueueB = new List<LocalTargetInfo>();
					foreach (Thing item in hashSet)
					{
						job.targetQueueB.Add(item);
					}
				}
				job.targetC = (Thing)c;
				job.count = neededTotal;
				job.haulMode = HaulMode.ToContainer;
				return job;
			}
			missingResources.Add(need2);
			if (FloatMenuMakerMap.makingFor != pawn)
			{
				break;
			}
		}
		if (missingResources.Count > 0 && FloatMenuMakerMap.makingFor == pawn)
		{
			JobFailReason.Is("MissingMaterials".Translate(missingResources.Select((ThingDefCountClass need) => need.Summary).ToCommaList()));
		}
		return null;
	}

	private void FindAvailableNearbyResources(Thing firstFoundResource, Pawn pawn, out int resTotalAvailable)
	{
		int num = Mathf.Min(firstFoundResource.def.stackLimit, pawn.carryTracker.MaxStackSpaceEver(firstFoundResource.def));
		resTotalAvailable = 0;
		resourcesAvailable.Clear();
		resourcesAvailable.Add(firstFoundResource);
		resTotalAvailable += firstFoundResource.stackCount;
		if (resTotalAvailable >= num)
		{
			return;
		}
		foreach (Thing item in GenRadial.RadialDistinctThingsAround(firstFoundResource.Position, firstFoundResource.Map, 5f, useCenter: false))
		{
			if (resTotalAvailable >= num)
			{
				break;
			}
			if (item.def == firstFoundResource.def && GenAI.CanUseItemForWork(pawn, item))
			{
				resourcesAvailable.Add(item);
				resTotalAvailable += item.stackCount;
			}
		}
	}

	private HashSet<Thing> FindNearbyNeeders(Pawn pawn, ThingDefCountClass need, IConstructible c, int resTotalAvailable, bool canRemoveExistingFloorUnderNearbyNeeders, out int neededTotal, out Job jobToMakeNeederAvailable)
	{
		neededTotal = need.count;
		HashSet<Thing> hashSet = new HashSet<Thing>();
		Thing thing = (Thing)c;
		foreach (Thing item in GenRadial.RadialDistinctThingsAround(thing.Position, thing.Map, 8f, useCenter: true))
		{
			if (neededTotal >= resTotalAvailable)
			{
				break;
			}
			if (IsNewValidNearbyNeeder(item, hashSet, c, pawn) && (!(item is Blueprint blue) || !ShouldRemoveExistingFloorFirst(pawn, blue)))
			{
				int num = GenConstruct.AmountNeededByOf((IConstructible)item, need.thingDef);
				if (num > 0)
				{
					hashSet.Add(item);
					neededTotal += num;
				}
			}
		}
		if (c is Blueprint blueprint && blueprint.def.entityDefToBuild is TerrainDef && canRemoveExistingFloorUnderNearbyNeeders && neededTotal < resTotalAvailable)
		{
			foreach (Thing item2 in GenRadial.RadialDistinctThingsAround(thing.Position, thing.Map, 3f, useCenter: false))
			{
				if (IsNewValidNearbyNeeder(item2, hashSet, c, pawn) && item2 is Blueprint blue2)
				{
					Job job = RemoveExistingFloorJob(pawn, blue2);
					if (job != null)
					{
						jobToMakeNeederAvailable = job;
						return hashSet;
					}
				}
			}
		}
		jobToMakeNeederAvailable = null;
		return hashSet;
	}

	private bool IsNewValidNearbyNeeder(Thing t, HashSet<Thing> nearbyNeeders, IConstructible constructible, Pawn pawn)
	{
		if (!(t is IConstructible) || t == constructible || t is Blueprint_Install || t.Faction != pawn.Faction || t.IsForbidden(pawn) || nearbyNeeders.Contains(t) || !GenConstruct.CanConstruct(t, pawn, checkSkills: false))
		{
			return false;
		}
		return true;
	}

	protected static bool ShouldRemoveExistingFloorFirst(Pawn pawn, Blueprint blue)
	{
		if (!(blue.def.entityDefToBuild is TerrainDef))
		{
			return false;
		}
		if (!pawn.Map.terrainGrid.CanRemoveTopLayerAt(blue.Position))
		{
			return false;
		}
		return true;
	}

	protected Job RemoveExistingFloorJob(Pawn pawn, Blueprint blue)
	{
		if (!ShouldRemoveExistingFloorFirst(pawn, blue))
		{
			return null;
		}
		if (!pawn.CanReserve(blue.Position, 1, -1, ReservationLayerDefOf.Floor))
		{
			return null;
		}
		if (pawn.WorkTypeIsDisabled(WorkGiverDefOf.ConstructRemoveFloors.workType))
		{
			return null;
		}
		Job job = JobMaker.MakeJob(JobDefOf.RemoveFloor, blue.Position);
		job.ignoreDesignations = true;
		return job;
	}

	private Job InstallJob(Pawn pawn, Blueprint_Install install)
	{
		Thing miniToInstallOrBuildingToReinstall = install.MiniToInstallOrBuildingToReinstall;
		IThingHolder parentHolder = miniToInstallOrBuildingToReinstall.ParentHolder;
		if (parentHolder != null && parentHolder is Pawn_CarryTracker pawn_CarryTracker)
		{
			JobFailReason.Is("BeingCarriedBy".Translate(pawn_CarryTracker.pawn));
			return null;
		}
		if (miniToInstallOrBuildingToReinstall.IsForbidden(pawn))
		{
			JobFailReason.Is(ForbiddenLowerTranslated);
			return null;
		}
		if (!pawn.CanReach(miniToInstallOrBuildingToReinstall, PathEndMode.ClosestTouch, pawn.NormalMaxDanger()))
		{
			JobFailReason.Is(NoPathTranslated);
			return null;
		}
		if (!pawn.CanReserve(miniToInstallOrBuildingToReinstall))
		{
			Pawn pawn2 = pawn.Map.reservationManager.FirstRespectedReserver(miniToInstallOrBuildingToReinstall, pawn);
			if (pawn2 != null)
			{
				JobFailReason.Is("ReservedBy".Translate(pawn2.LabelShort, pawn2));
			}
			return null;
		}
		Job job = JobMaker.MakeJob(JobDefOf.HaulToContainer);
		job.targetA = miniToInstallOrBuildingToReinstall;
		job.targetB = install;
		job.count = 1;
		job.haulMode = HaulMode.ToContainer;
		return job;
	}
}
